name: Sync Renovate changeset
on:
  pull_request_target:
    paths:
      - '.github/workflows/sync_renovate-changesets.yml'
      - '**/yarn.lock'

jobs:
  generate-changeset:
    runs-on: ubuntu-latest
    if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage'
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          fetch-depth: 2
          ref: ${{ github.head_ref }}
          token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
      - name: Configure Git
        run: |
          git config --global user.email noreply@backstage.io
          git config --global user.name 'Github changeset workflow'
      - name: Generate changeset
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            const { promises: fs } = require("fs");
            // Parses package.json files and returns the package names
            async function getPackagesNames(files) {
              const names = [];
              for (const file of files) {
                const data = JSON.parse(await fs.readFile(file, "utf8"));
                if (!data.private) {
                  names.push(data.name);
                }
              }
              return names;
            }

            async function createChangeset(fileName, packageBumps, packages) {
              let message = "";
              for (const [pkg, bump] of packageBumps) {
                message = message + `Updated dependency \`${pkg}\` to \`${bump}\`.\n`;
              }

              const pkgs = packages.map((pkg) => `'${pkg}': patch`).join("\n");
              const body = `---\n${pkgs}\n---\n\n${message.trim()}\n`;
              await fs.writeFile(fileName, body);
            }

            async function getBumps(files) {
              const bumps = new Map();
              for (const file of files) {
                const { stdout: changes } = await exec.getExecOutput("git", [
                  "show",
                  file,
                ]);
                for (const change of changes.split("\n")) {
                  if (!change.startsWith("+ ")) {
                    continue;
                  }
                  const match = change.match(/"(.*?)"/g);
                  bumps.set(match[0].replace(/"/g, ""), match[1].replace(/"/g, ""));
                }
              }
              return bumps;
            }

            const branch = await exec.getExecOutput("git branch --show-current");
            if (!branch.stdout.startsWith("renovate/")) {
              console.log("Not a renovate branch, skipping");
              return;
            }
            const diffOutput = await exec.getExecOutput("git diff --name-only HEAD~1");
            const diffFiles = diffOutput.stdout.split("\n");
            if (diffFiles.find((f) => f.startsWith(".changeset"))) {
              console.log("Changeset already exists, skipping");
              return;
            }
            const files = diffFiles
              .filter((file) => file !== "package.json") // skip root package.json
              .filter((file) => file.includes("package.json"));
            const packageNames = await getPackagesNames(files);
            if (!packageNames.length) {
              console.log("No package.json changes to published packages, skipping");
              return;
            }
            const { stdout: shortHash } = await exec.getExecOutput(
              "git rev-parse --short HEAD"
            );
            const fileName = `.changeset/renovate-${shortHash.trim()}.md`;

            const packageBumps = await getBumps(files);
            await createChangeset(fileName, packageBumps, packageNames);
            await exec.exec("git", ["add", fileName]);
            await exec.exec("git commit -C HEAD --amend --no-edit");
            await exec.exec("git push --force");
